#!/usr/bin/env php
<?php
$SOAR_DIR=getenv("SOAR_DIR");
if(empty($SOAR_DIR)) $SOAR_DIR="/opt/soar";
$SOAR_DIR="../../../../..";
include "$SOAR_DIR/common/php/soar/soar.inc";

class stat
{/*{{{*/
    public static $client=array();  // ip:port => call_num
    public static $client_conn_num=0;
    public static $svc=array();     // name => call_num
}/*}}}*/

function load_cnfg()
{/*{{{*/
    $cnfg_file=dirname(__FILE__)."/server.cnfg.inc";
    include $cnfg_file;

    if(empty(cnfg::$ACCESS['SERVER_PORT']) ||
            cnfg::$ACCESS['SERVER_PORT'] < 1024)
    {
        printf("%s: ACCESS.SERVER_PORT invalid\n", __METHOD__);
        return false;
    }
    if(empty(cnfg::$ACCESS['MONITOR_PORT']) ||
            cnfg::$ACCESS['MONITOR_PORT'] < 1024)
    {
        printf("%s: ACCESS.MONITOR_PORT invalid\n", __METHOD__);
        return false;
    }

    return true;
}/*}}}*/

class worker
{/*{{{*/
    private static $CONSOLE_IDLE_TIMEOUT=300;
    private static $CONN_IDLE_TIMEOUT=120;

    public static $listen_slots=array();    // 'server'=> 'monitor'=>
    public static $ep=null;
    
    private static $quit=false;
    private static $monitor_login=-1;
    private static $cmd_map=array(
            array('name'=>"printclient", 'ab'=>"pclt",
                'desc'=>"print client ip & port",
                'usage'=>"printclient(pwrk)"),
            array('name'=>"printservice", 'ab'=>"psvc",
                'desc'=>"print service status",
                'usage'=>"printservice(psvc)"),
            array('name'=>"shutdown", 'ab'=>"shutdown",
                'desc'=>"shutdown server",
                'usage'=>"shutdown"),
            array('name'=>"help", 'ab'=>"h",
                'desc'=>"",
                'usage'=>"help(h) [command]")
                );

    private function CMD_FUNC_PRINTCLIENT($request)
    {/*{{{*/
        $respond=
            "Client                         Calls\n".
            "------------                   --------\n";
        foreach(stat::$client as $addr=>$call_num)
        {
            $respond.=sprintf("%-30s %8d\n",
                    $addr, $call_num);
        }
        $respond.=
            "----------------------------------------\n";
        $respond.="Connected: ".stat::$client_conn_num."\n";
        return $respond;
    }/*}}}*/

    private function CMD_FUNC_PRINTSERVICE($request)
    {/*{{{*/
        $respond=
            "Service                        Calls\n".
            "-------                        ---------\n";
        foreach(stat::$svc as $name=>$call_num)
        {
            $respond.=sprintf("%-30s  %9d\n",
                    $name, $call_num);
        }
        return $respond;
    }/*}}}*/

    private function CMD_FUNC_SHUTDOWN($request)
    {/*{{{*/
        self::$quit=true;
        $respond="Shutdown complete\nDisconnect\n";
        return $respond;
    }/*}}}*/

    private function CMD_FUNC_HELP($request)
    {/*{{{*/
        if(empty($request))
        {
            $respond="help [command]\ncommands:\n";
            foreach(self::$cmd_map as $arr)
            {
                $respond.="    ".$arr['name']."\n";
            }
            return $respond;
        }

        $request=strtolower($request);
        foreach(self::$cmd_map as $arr)
        {
            if($arr['name'] == $request || $arr['ab'] == $request)
            {
                $respond="Description: ".$arr['desc']."\n".
                    "Usage: ".$arr['usage']."\n";
                return $respond;
            }
        }
        $respond="Command not found, type help\n";
        return $respond;
    }/*}}}*/

    private function callcmd($request)
    {/*{{{*/
        $request=trim($request);
        $pos=strpos($request, " ");
        if($pos === false)
        {
            $cmd=$request;
            $body="";
        }
        else
        {
            $cmd=substr($request, 0, $pos);
            $body=substr($request, $pos);
            $body=ltrim($body);
        }
        $cmd=strtolower($cmd);
        //printf("cmd(%s)\n", $cmd);
        $cmdsize=strlen($cmd);
        if(0 < $cmdsize && $cmdsize <= misc::NAME_SIZE) 
        {
            $found=false;
            foreach(self::$cmd_map as $arr)
            {
                if($arr['name'] == $cmd || $arr['ab'] == $cmd)
                {
                    $found=true;
                    $cmd=$arr['name'];
                    break;
                }
            }
            if($found)
            {
                $func="CMD_FUNC_".strtoupper($cmd);
                $respond=self::$func($body);
            }
            else
            {
                $respond="command(".$cmd.") not found\n";
            }
        }
        else
        {
            $respond="command invalid\n";
        }

        return $respond;
    }/*}}}*/

    private function callsvc($request)
    {/*{{{*/
        $respond="";
        $curr=0;
        $svc=para::get_item($request, $curr);
        if(empty($svc) || strlen($svc) > misc::NAME_SIZE)
        {
            errlog::add("%s: svc(%s) invalid",
                    __METHOD__,
                    empty($svc)?"null":$svc);
            para::put_item($respond, SOAR_ERR_SVC_INVALID); 
        }

        @include_once "./svc/${svc}.inc";
        $func="SVC_FUNC_$svc";
        if(function_exists($func))
        {
            $respond=$func($request);
            if(array_key_exists($svc, stat::$svc))
                ++stat::$svc[$svc];
            else
                stat::$svc[$svc]=1;
        }
        else
        {
            errlog::add("%s: func(%s) not found",
                    __METHOD__, $svc);
            para::put_item($respond, SOAR_ERR_SVC_NOT_FOUND);
        }

        return $respond;
    }/*}}}*/

    private function conn($active)
    {/*{{{*/
        if($active['slot_id'] == self::$listen_slots['monitor'])
        {
            if(self::$monitor_login >= 0)
            {
                errlog::add("%s: monitor has logged in", __METHOD__);
                socket_close($active['sock']);
                return false;
            }
        }
        $new_slot=self::$ep->add($active['new_sock']);
        if($new_slot === false)
        {
            errlog::add("%s: ep::add fail", __METHOD__);
            socket_close($active['new_sock']);
            return false;
        }
        if($active['slot_id'] == self::$listen_slots['monitor'])
        {
            self::$ep->set_timeout($new_slot,
                    self::$CONSOLE_IDLE_TIMEOUT);
            self::$monitor_login=$new_slot;
            errlog::add("%s: monitor log in", __METHOD__);
        }
        else
        {
            self::$ep->set_timeout($new_slot,
                    self::$CONN_IDLE_TIMEOUT);
            self::$ep->set_wrap($new_slot);
            $slot_info=self::$ep->get_info($new_slot);
            $addr=$slot_info['peer_ip'].":".$slot_info['peer_port'];
            stat::$client[$addr]=0;
            ++stat::$client_conn_num;
            //print "conn:$new_slot\n";
        }
        return true;
    }/*}}}*/

    private function recv($active)
    {/*{{{*/
        if($active['slot_id'] == self::$monitor_login)
        {
            $respond=self::callcmd($active['msg']);
        }
        else
        {
            $respond=self::callsvc($active['msg']);
        }
        $res=self::$ep->send($active['slot_id'], $respond);
        if($res == false)
        {
            errlog::add("%s: send fail", __METHOD__); 
        }
    }/*}}}*/

    private function disconn($active)
    {/*{{{*/
        if($active['slot_id'] == self::$monitor_login)
        {
            self::$monitor_login=-1;
            errlog::add("%s: monitor %s revoke, error:%d",
                    __METHOD__, $active['addr'], $active['error']);
        }
        else
        {
            if(array_key_exists($active['addr'], stat::$client))
                unset(stat::$client[$active['addr']]);
            errlog::add("%s: client %s %s, error:%d, total:%d, ",
                    __METHOD__, $active['addr'],
                    ($active['type']==ep::ABORT)?"abort":"timeout",
                    $active['error'],
                    count(stat::$client));
        }
        return true;
    }/*}}}*/

    public function start()
    {/*{{{*/
        while(!self::$quit)
        {
            $active_list=self::$ep->poll();
            if($active_list === false)
            {
                errlog::add("%s: ep::poll fail", __METHOD__);
            }
            else
            {
                foreach($active_list as $active)
                {
                    //print_r($active);
                    switch($active['type'])
                    {
                        case ep::CONN:
                            self::conn($active);
                            break;
                        case ep::RECV:
                            self::recv($active);
                            break;
                        case ep::ABORT:
                        case ep::TIMEOUT:
                            self::disconn($active);
                            break;
                    }
                }
            }
        }
    }/*}}}*/
}/*}}}*/

function start($daemon)
{/*{{{*/
    if($daemon)
    {
        $pid=pcntl_fork();
        if($pid == -1)
        {
            printf("%s: daemonize fail", __METHOD__);
            return false;
        }
        if($pid > 0)
        {
            return true;
        }
        // errlog
        if(errlog::init(basename(__FILE__), cnfg::$ERRLOG['LOCATION'])
                == false)
        {
            printf("%s: init errlog fail", __METHOD__);
            return false;
        }
    }

    // setuid
    if(posix_getuid() == 0)
    {
        if(!empty(cnfg::$USER))
        {
            $user=posix_getpwnam(cnfg::$USER);
            if($user)
            {
                if(posix_setuid($user['uid']) == false)
                {
                    printf("Change to user %s fail\n", cnfg::$USER);
                    return false;
                }
                printf("Change to user %s\n", cnfg::$USER);
            }
            else
            {
                printf("User %s not found, keep root\n", cnfg::$USER);
            }
        }
        else
        {
            printf("User empty, keep root\n");
        }
    }

    stat::$client=array();
    stat::$client_conn_num=0;
    stat::$svc=array();

    $ep=new ep();

    $sock=sock::listen(cnfg::$ACCESS['MONITOR_PORT']);
    if($sock === false)
    {
        errlog::add("%s: open monitor_port(%d) fail",
                __METHOD__, cnfg::$ACCESS['MONITOR_PORT']);
        return false;
    }
    $slot_id=$ep->add_listen($sock);
    if($slot_id === false)
    {
        errlog::add("%s: ep::add_listen monitor fail", __METHOD__);
        return false;
    }
    worker::$listen_slots['monitor']=$slot_id;
    printf("open monitor port:%d slot:%d\n",
            cnfg::$ACCESS['MONITOR_PORT'], $slot_id);
    errlog::add("%s: open monitor port %d",
            __METHOD__, cnfg::$ACCESS['MONITOR_PORT']);

    $sock=sock::listen(cnfg::$ACCESS['SERVER_PORT']);
    if($sock === false)
    {
        printf("%s: open server_port(%d) fail",
                __METHOD__, cnfg::$ACCESS['SERVER_PORT']);
        return false;
    }
    $slot_id=$ep->add_listen($sock);
    if($slot_id === false)
    {
        errlog::add("%s: ep::add_listen server fail", __METHOD__);
        return false;
    }
    worker::$listen_slots['server']=$slot_id;
    printf("open server port:%d slot:%d\n",
            cnfg::$ACCESS['SERVER_PORT'], $slot_id);
    errlog::add("%s: open server port %d",
            __METHOD__, cnfg::$ACCESS['SERVER_PORT']);

    worker::$ep=$ep;

    return worker::start();
}/*}}}*/

function main($argv)
{/*{{{*/
    if(load_cnfg() == false)
        return 1;
    $daemon=false;
    if(isset($argv[1]))
    {
        if($argv[1] == "-d" || $argv[1] == "--daemon")
           $daemon=true; 
    }
    if(start($daemon) == false)
        return 1;
    return 0;
}/*}}}*/

exit(main($argv));
?>
